﻿using System;
using System.Collections.Generic;


namespace advadev.CanOpen
{

    /// <summary>
    /// Abstract base class of all can open integer data blocks
    /// </summary>
    abstract public class CanOpenIntegerDataBlock : ICanOpenValueObject
    {

        /// <summary>for internal use</summary>
        private List<long> _value = new List<long>();


        /// <summary>
        /// Initializes a new instance of the CanOpenIntegerDataBlock class by
        /// the given
        /// parameter
        /// </summary>
        /// <param name="byteLength">
        /// Byte length (must be in the range of 1..8)
        /// </param>
        /// <param name="index">
        /// Index of the object
        /// </param>
        /// <param name="subIndex">
        /// Sub index of the object
        /// </param>
        /// <param name="isReadable">
        /// Flag indicating whether the object is readable
        /// </param>
        /// <param name="isWriteable">
        /// Flag indicating whether the object is writeable
        /// </param>
        internal protected CanOpenIntegerDataBlock(uint byteLength, UInt16 index, byte subIndex, bool isReadable, bool isWriteable)
        {
            ByteLength = byteLength;
            BitLength = byteLength * 8;
            Index = index;
            SubIndex = subIndex;
            IsReadable = isReadable;
            IsWriteable = isWriteable;
            ByteOrder = CanOpenByteOrder.LittleEndian;
            LastServiceDataObjectUploadSequence = new List<CanOpenMessage>();
            LastServiceDataObjectDownloadSequence = new List<CanOpenMessage>();
        }


        /// <summary>
        /// Gets or sets the value
        /// </summary>
        /// <remarks>
        /// All values in the list will be shifted until the are in the valid
        /// range
        /// </remarks>
        public List<long> Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (value != _value)
                {
                    List<long> oldValue = _value;
                    _value = new List<long>();
                    if (value != null)
                    {
                        foreach (long item in value)
                        {
                            _value.Add(getShiftedValue(item));
                        }
                    }
                    onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                }
            }
        }

        /// <summary>
        /// Gets the minimum value
        /// </summary>
        private long MinValue
        {
            get
            {
                return -1 * ((long)Math.Pow(2, BitLength - 1));
            }
        }

        /// <summary>
        /// Gets the maximum value
        /// </summary>
        private long MaxValue
        {
            get
            {
                if (ByteLength == 8)
                {
                    return long.MaxValue;
                }
                return (long)Math.Pow(2, BitLength - 1) - 1;
            }
        }

        /// <summary>
        /// Gets the byte length
        /// </summary>
        private uint ByteLength
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the bit length
        /// </summary>
        private uint BitLength
        {
            get;
            set;
        }


        /// <summary>
        /// Gets the shifted value
        /// </summary>
        /// <param name="value">
        /// Value
        /// </param>
        /// <returns>
        /// Shifted value
        /// </returns>
        private long getShiftedValue(long value)
        {
            long shiftedValue = value;
            if (value > MaxValue)
            {
                long difference = value - MaxValue;
                long range = (long)Math.Pow(2, BitLength);
                if (difference > range)
                {
                    difference -= ((long)Math.Floor((double)difference / (double)range) * range);
                }
                shiftedValue = MinValue + (difference - 1);
            }
            if (value < MinValue)
            {
                long difference = MinValue - value;
                long range = (long)Math.Pow(2, BitLength);
                if (difference > range)
                {
                    difference -= ((long)Math.Floor((double)difference / (double)range) * range);
                }
                shiftedValue = MaxValue - (difference - 1);
            }
            return shiftedValue;
        }

        /// <summary>
        /// Raises the value changing event
        /// </summary>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void onRaiseValueChanging(ValueChangingEventArgs args)
        {
            if (_valueChanging != null)
            {
                _valueChanging(this, args);
            }
        }

        /// <summary>
        /// Raises the value changed event
        /// </summary>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void onRaiseValueChanged(ValueChangedEventArgs args)
        {
            if (_valueChanged != null)
            {
                _valueChanged(this, args);
            }
        }


        #region ICanOpenValueObject implementation
        /// <summary>for internal use</summary>
        private event ValueChangingEventHandler _valueChanging;
        /// <summary>for internal use</summary>
        private event ValueChangedEventHandler _valueChanged;
        /// <summary>for internal use</summary>
        private bool _isValueChangedEventRaisingPaused;
        /// <summary>for internal use</summary>
        private List<long> _oldValue;

        /// <summary>
        /// Occures when the value is changing
        /// </summary>
        public event ValueChangingEventHandler ValueChanging
        {
            add
            {
                _valueChanging += value;
            }
            remove
            {
                _valueChanging -= value;
            }
        }

        /// <summary>
        /// Occures when the value is changed
        /// </summary>
        public event ValueChangedEventHandler ValueChanged
        {
            add
            {
                _valueChanged += value;
            }
            remove
            {
                _valueChanged -= value;
            }
        }

        /// <summary>
        /// Gets the index of the object
        /// </summary>
        public UInt16 Index
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the sub index of the object
        /// </summary>
        public byte SubIndex
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the object is readable
        /// </summary>
        public bool IsReadable
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the object is writeable
        /// </summary>
        public bool IsWriteable
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a flag indicating whether a expedited download transfer is
        /// possible for this object
        /// </summary>
        bool ICanOpenValueObject.IsExpeditedDownloadTransferPossible
        {
            get
            {
                return (Value.Count * ByteLength <= 4);
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a expedited upload transfer is
        /// possible for this object
        /// </summary>
        bool ICanOpenValueObject.IsExpeditedUploadTransferPossible
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a block download transfer is advised
        /// for this object
        /// </summary>
        bool ICanOpenValueObject.IsBlockDownloadTransferAdvised
        {
            get
            {
                return (Value.Count * ByteLength > 28);
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a block upload transfer is advised
        /// for this object
        /// </summary>
        bool ICanOpenValueObject.IsBlockUploadTransferAdvised
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the value changed event
        /// raising is paused
        /// </summary>
        public bool IsValueChangedEventRaisingPaused
        {
            get
            {
                return _isValueChangedEventRaisingPaused;
            }
            set
            {
                if (value != _isValueChangedEventRaisingPaused)
                {
                    _isValueChangedEventRaisingPaused = value;
                    if (!_isValueChangedEventRaisingPaused)
                    {
                        if (_value != _oldValue)
                        {
                            onRaiseValueChanged(new ValueChangedEventArgs(_oldValue, _value));
                        }
                        _oldValue = _value;
                    }
                    else
                    {
                        _oldValue = _value;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the byte order
        /// </summary>
        public CanOpenByteOrder ByteOrder
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the last service data object upload sequence
        /// </summary>
        public IList<CanOpenMessage> LastServiceDataObjectUploadSequence
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the last service data object download sequence
        /// </summary>
        public IList<CanOpenMessage> LastServiceDataObjectDownloadSequence
        {
            get;
            private set;
        }


        /// <summary>
        /// Converts the value of the object to a byte array
        /// </summary>
        /// <returns>
        /// Value of the object converted to a byte array
        /// </returns>
        IList<byte> ICanOpenValueObject.ToByteArray()
        {
            IList<byte> result = new List<byte>();
            foreach (long longValue in Value)
            {
                ulong uLongValue = 0;
                if (longValue < 0)
                {
                    uLongValue = (ulong)~longValue;
                    byte[] ulongBytes = BitConverter.GetBytes(uLongValue);
                    // -->> TODO: Create unit tests!!!
                    if (ByteOrder == CanOpenByteOrder.LittleEndian)
                    {
                        for (int i = 0; i < ByteLength; i++)
                        {
                            result.Add((byte)~ulongBytes[i]);
                        }
                    }
                    else
                    {
                        for (int i = (int)(ByteLength - 1); i >= 0; i--)
                        {
                            result.Add((byte)~ulongBytes[i]);
                        }
                    }
                    // <<-- TODO
                }
                else
                {
                    byte[] bytes = BitConverter.GetBytes(longValue);
                    // -->> TODO: Create unit tests!!!
                    if (ByteOrder == CanOpenByteOrder.LittleEndian)
                    {
                        for (int i = 0; i < ByteLength; i++)
                        {
                            result.Add(bytes[i]);
                        }
                    }
                    else
                    {
                        for (int i = (int)(ByteLength - 1); i >= 0; i--)
                        {
                            result.Add(bytes[i]);
                        }
                    }
                    // <<-- TODO
                }
            }
            if (result.Count == 0)
            {
                result.Add(0);
            }
            return result;
        }

        /// <summary>
        /// Converts the given byte array and sets the result as value
        /// </summary>
        /// <param name="byteArray">
        /// Byte array to convert and set as value
        /// </param>
        void ICanOpenValueObject.FromByteArray(IList<byte> byteArray)
        {
            try
            {
                List<long> newValue = new List<long>();
                for (int i = 0; i < byteArray.Count; i += (int)ByteLength)
                {
                    byte[] bytes = new byte[8];
                    for (int j = 0; j < ByteLength; j++)
                    {
                        if ((i + j) < byteArray.Count)
                        {
                            // -->> TODO: Create unit tests!!!
                            if (ByteOrder == CanOpenByteOrder.LittleEndian)
                            {
                                bytes[j] = byteArray[i + j];
                            }
                            else
                            {
                                bytes[j] = byteArray[i + ((int)ByteLength - 1) - j];
                            }
                            // <<-- TODO
                        }
                    }
                    newValue.Add(getShiftedValue(BitConverter.ToInt64(bytes, 0)));
                }
                List<long> oldValue = _value;
                _value = newValue;
                if (_value != oldValue)
                {
                    if (!IsValueChangedEventRaisingPaused)
                    {
                        onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                    }
                }
            }
            catch { }
        }

        /// <summary>
        /// Converts the given byte array and sets the result as value after
        /// validating the value
        /// </summary>
        /// <param name="byteArray">
        /// Byte array to convert and set as value
        /// </param>
        /// <param name="abortCode">
        /// Abort code if the validation failed (otherwise null)
        /// </param>
        /// <returns>
        /// True, if the new value is valid, false otherwise
        /// </returns>
        bool ICanOpenValueObject.FromByteArray(IList<byte> byteArray, out CanOpenAbortCode abortCode)
        {
            try
            {
                List<long> newValue = new List<long>();
                for (int i = 0; i < byteArray.Count; i += (int)ByteLength)
                {
                    byte[] bytes = new byte[8];
                    for (int j = 0; j < ByteLength; j++)
                    {
                        if ((i + j) < byteArray.Count)
                        {
                            // -->> TODO: Create unit tests!!!
                            if (ByteOrder == CanOpenByteOrder.LittleEndian)
                            {
                                bytes[j] = byteArray[i + j];
                            }
                            else
                            {
                                bytes[j] = byteArray[i + ((int)ByteLength - 1) - j];
                            }
                            // <<-- TODO
                        }
                    }
                    newValue.Add(getShiftedValue(BitConverter.ToInt64(bytes, 0)));
                }
                ValueChangingEventArgs args = new ValueChangingEventArgs(_value, newValue);
                onRaiseValueChanging(args);
                if (args.AbortCode != null)
                {
                    abortCode = args.AbortCode;
                    return false;
                }
                List<long> oldValue = _value;
                _value = newValue;
                if (_value != oldValue)
                {
                    if (!IsValueChangedEventRaisingPaused)
                    {
                        onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                    }
                }
            }
            catch
            {
                abortCode = new CanOpenAbortCode(0x30, 0x00, 0x09, 0x06);
                return false;
            }
            abortCode = null;
            return true;
        }
        #endregion ICanOpenValueObject implementation

    }

}
